local _, ns = ...

local Rotor = _G.Rotor
local tinsert = _G.table.insert
local tremove = _G.table.remove
local format = _G.string.format
local CreateFrame = _G.CreateFrame
local UIParent = _G.UIParent
local iif = ns.iif
----------------------------------------
local tex = [[Interface\AddOns\rotor\media\bar.tga]]
----------------------------------------
local RotorBar = {
    visible = false,
    loc = 'FREE', -- FREE, TOP, BOTTOM
    last_int_bordercolor = -1,
    last_int_barcolor = -1,
    cur_value = 0,
    min_value = 0,
    max_value = 100,
    auto_hide = false, -- hide when cur at min/max, else show
    on_delete = nil,
    sliding = false,
    yoff = 0, -- 0 -- free pos bar or hidden sliding bar, < 0 -- bottom bar, > 0 -- top bar
    ymult = 0, -- shift multiplier, -1 -- shift down, 1 -- shift up
}
----------------------------------------
local bars = {}
----------------------------------------
local function make_int_color(r_prop, g_prop, b_prop, a_prop)
    local value = math.floor(r_prop * 255) * 65536 + math.floor(g_prop * 255) * 256 + math.floor(b_prop * 255)
    if a_prop then
        value = value + math.floor(a_prop * 255) * 16777216
    end
    return value
end
----------------------------------------
function Rotor:createBar(loc, sliding, createLeftText, createCenterText, createRightText, createSpark)
    local bar = CreateFrame('Frame')
    for k, v in pairs(RotorBar) do
        bar[k] = v
    end
    bar:init(loc, sliding, createLeftText, createCenterText, createRightText, createSpark)
    return bar
end
----------------------------------------
function Rotor:updateBars(elapsed, force)
    for _, bar in pairs(bars) do
        if bar.update then
            bar:update(elapsed, force)
        end
    end
end
----------------------------------------
function RotorBar:delete()
    self:hide(true)
    if self.on_delete then
        self:on_delete()
    end

    local my_ix
    for ix, bar in pairs(bars) do
        if bar == self then
            my_ix = ix
            break
        end
    end
    if not my_ix then
        error('Rotor: not found bar to remove')
    end
    tremove(bars, my_ix)
end
----------------------------------------
local function createText(bar, align, condensed, font_size)
	local text
    local xoff, yoff

    text = bar:CreateFontString(nil, 'OVERLAY', bar)

    if align == 'LEFT' then
        xoff, yoff = 2, 0
    elseif align == 'RIGHT' then
        xoff, yoff = -2, 0
    else
        xoff, yoff = 0, 0
    end
    text:ClearAllPoints()
	text:SetPoint(align, bar, align, xoff, yoff)
	text:SetJustifyH(align)
    text:SetJustifyV('CENTER')
	text:SetTextColor(1, 1, 1)
    text:SetWidth(bar:GetWidth() - 4)
    text:SetHeight(bar:GetHeight())
    text:SetFont(iif(condensed, Rotor.cfg.condensed_font, Rotor.cfg.font), font_size or Rotor.cfg.font_size, 'THINOUTLINE')
    text:SetShadowColor(0, 0, 0, 0.4)
    text:SetShadowOffset(1, -1)

    return text
end
----------------------------------------
function RotorBar:initialYOff()
    return (Rotor.main_icon_size / 2 + Rotor.icon_size + Rotor.icon_spacing + self:GetHeight() / 2) * self.ymult
end
----------------------------------------
function RotorBar:init(loc, sliding, createLeftText, createCenterText, createRightText, createSpark)
    local height = Rotor.icon_size / 2

    self:Hide()
    self:SetScale(Rotor.cfg.framescale)
    self:SetBackdrop({
        bgFile = 'Interface\\Buttons\\WHITE8x8',
        edgeFile = 'Interface\\Buttons\\WHITE8x8',
        tile = false, tileSize = 0, edgeSize = 1,
        insets = {left = -1, right = -1, top = -1, bottom = -1}
    })
    self:SetBackdropColor(unpack(Rotor.cfg.backdropcolor))
    self:setBorderColor(Rotor.cfg.bordercolor)
    self:SetWidth(Rotor.icon_size * 4 + Rotor.icon_spacing * 3)
    self:SetHeight(height)

    self.bar = CreateFrame('StatusBar', nil, self)
	self.bar:ClearAllPoints()
	self.bar:SetPoint('CENTER', self, 'CENTER', 0, 0)
	self.bar:SetStatusBarTexture(Rotor.cfg.bar_texture or tex)
	self.bar:SetMinMaxValues(0, 100)
	self.bar:SetValue(0)
    self.bar:SetScale(Rotor.cfg.framescale)
    self.bar:SetWidth(self:GetWidth() - 2)
    self.bar:SetHeight(height - 2)

    if createSpark then
        self.spark = self.bar:CreateTexture(nil, 'OVERLAY', nil)
        self.spark:SetTexture([[Interface\CastingBar\UI-CastingBar-Spark]])
        self.spark:SetWidth(12)
        self.spark:SetBlendMode('ADD')
        self.spark:SetHeight(height * 1.85)
        self.spark:Show()
    end

    self:setColor({0.5, 0, 0})

    if createLeftText then
        self.left_text = createText(self.bar, 'LEFT')
    end
    if createRightText then
        self.right_text = createText(self.bar, 'RIGHT')
    end
    if createCenterText then
        self.center_text = createText(self.bar, 'CENTER')
    end

    self.sliding = sliding

    local relative_to
    local yoff = 0

    if (loc == 'TOP') or (loc == 'BOTTOM') then
        if loc == 'TOP' then
            self.ymult = 1
        else
            self.ymult = -1
        end

        relative_to = Rotor.main_icon
        if not sliding then
            -- find place to insert
            yoff = self:initialYOff()
            for _, bar in pairs(bars) do
                if not bar.sliding and bar.ymult == self.ymult then
                    yoff = yoff + bar:GetHeight() * bar.ymult
                end
            end
            -- shift visible sliding bars
            local yshift = height * self.ymult
            for _, bar in pairs(bars) do
                if bar.sliding and (bar.ymult == self.ymult) then
                    bar:moveVerticallyBy(yshift)
                end
            end
        end
    else
        assert(not sliding, 'free pos sliding bar')
        relative_to = UIParent
        self.ymult = 0
    end
    tinsert(bars, self)

    self:ClearAllPoints()
    self:SetPoint('CENTER', relative_to, 'CENTER', 0, yoff)
    self.yoff = yoff
end
----------------------------------------
function RotorBar:moveVerticallyBy(delta)
    if delta ~= 0 then
        local relative_to

        self.yoff = self.yoff + delta
        if self.ymult == 0 then
            relative_to = UIParent
        else
            relative_to = Rotor.main_icon
        end
        self:SetPoint('CENTER', relative_to, 'CENTER', 0, self.yoff)
    end
end
----------------------------------------
function RotorBar:heightChanged(old_height, new_height)
    if self.yoff == 0 then -- free/hidden
        return
    end

    if not new_height then
        new_height = self:GetHeight()
    end

    if new_height == old_height then
        return
    end

    local yshift = (new_height - old_height) * self.ymult
    local abs_old_yoff = self.yoff * self.ymult

    if new_height > old_height then
        abs_old_yoff = abs_old_yoff + self.ymult -- to do not move self
    end

    for _, bar in pairs(bars) do
        if bar.ymult == self.ymult and (bar.yoff * bar.ymult >= abs_old_yoff) then
            bar:moveVerticallyBy(yshift)
        end
    end
end
----------------------------------------
function RotorBar:setHeight(new_height)
    local old_height = self:GetHeight()
    if new_height == old_height then
        return
    end

    self:SetHeight(new_height)
	self.bar:SetHeight(new_height - 2)
    if self.spark then
        self.spark:SetHeight(new_height * 1.85)
    end
    self:textsDo(function(text) text:SetHeight(new_height - 2) end)
    self:heightChanged(old_height)
end
----------------------------------------
function RotorBar:textsDo(func, ...)
    if self.left_text then func(self.left_text, ...) end
    if self.center_text then func(self.center_text, ...) end
    if self.right_text then func(self.right_text, ...) end
end
----------------------------------------
function RotorBar:setWidth(new_width)
    self:SetWidth(new_width)
    self.bar:SetWidth(new_width - 2)
    self:textsDo(function(text) text:SetWidth(new_width - 6) end)
end
----------------------------------------
function RotorBar:hide(before_delete)
    if self.visible then
        self:Hide()
        self.visible = false
        if self.sliding or before_delete then
            -- shift remaining bars
            local abs_my_yoff = self.yoff * self.ymult
            local yshift = -(self:GetHeight() * self.ymult)
            for _, bar in pairs(bars) do
                if (bar.ymult == self.ymult) and (bar.yoff * bar.ymult > abs_my_yoff) then
                    bar:moveVerticallyBy(yshift)
                end
            end
            if self.sliding then
                self.yoff = 0 -- remove from sliding process
            end
        end
    end
end
----------------------------------------
function RotorBar:show()
    if not self.visible then
        if self.sliding then
            -- place as last bar
            local yoff = self:initialYOff()
            for _, bar in pairs(bars) do
                if (bar.ymult == self.ymult) and (bar.yoff ~= 0) then
                    yoff = yoff + bar:GetHeight() * bar.ymult
                end
            end
            self:moveVerticallyBy(yoff)
        end
        self:Show()
        self.visible = true
    end
end
----------------------------------------
function RotorBar:setValue(new_value, ...) -- with texts for existing fields in order (left, center, right)
    if new_value ~= self.cur_value then
        self.bar:SetValue(new_value)
        self.cur_value = new_value
        if self.spark then
            if (new_value == self.min_value) or (new_value == self.max_value) then
                self.spark:Hide()
            else
                self.spark:SetPoint('CENTER', self.bar, 'LEFT', self.bar:GetWidth() * (self.cur_value - self.min_value) / (self.max_value - self.min_value), 0)
                self.spark:Show()
            end
        end
    end
    if ... then
        local args = {...}
        local arg_index = 1

        self:textsDo(
            function(txt)
                if args[arg_index] then
                    txt:SetText(args[arg_index])
                    arg_index = arg_index + 1
                end
            end)
    end
end
----------------------------------------
function RotorBar:checkAutoHide()
    if self.auto_hide then
        if (self.cur_value <= self.min_value) or (self.cur_value >= self.max_value) then
            self:hide()
        else
            self:show()
        end
    end
end
----------------------------------------
function RotorBar:setValues(min_value, max_value, cur_value)
    cur_value = cur_value or self.cur_value
    min_value = min_value or self.min_value
    max_value = max_value or self.max_value
    if cur_value > max_value then cur_value = max_value end
    if cur_value < min_value then cur_value  = min_value end
    if (self.min_value ~= min_value) or (self.max_value ~= max_value) then
        self.bar:SetMinMaxValues(min_value, max_value)
        self.min_value = min_value
        self.max_value = max_value
    end
    self:setValue(cur_value)
    self:checkAutoHide()
end
----------------------------------------
--[[
function RotorBar:update(min_value, max_value, cur_value, left_text, center_text, right_text)
    self:setValues(min_value, max_value, cur_value)
    self:setTexts(left_text, center_text, right_text)
    local new_visible
    if hide_if_cur_eq_min then
        new_visible = cur_value > min_value
    elseif hide_if_cur_eq_max then
        new_visible = cur_value < max_value
    else
        new_visible = self.visible
    end
    if (new_visible ~= self.visible) then
        if self.visible then
            self:hide()
        else
            self:show()
        end
    end
end
]]
----------------------------------------
function RotorBar:setTexts(left_text, center_text, right_text)
    if left_text and self.left_text then self.left_text:SetText(left_text) end
    if right_text and self.right_text then self.right_text:SetText(right_text) end
    if center_text and self.center_text then self.center_text:SetText(center_text) end
end
----------------------------------------
function RotorBar:setBorderColor(color)
    local r, g, b, a = unpack(color)
    local int_color = make_int_color(r, g, b, a)
    if self.last_int_bordercolor ~= int_color then
        self:SetBackdropBorderColor(r, g, b, a)
        self.last_int_bordercolor = int_color
    end
end
----------------------------------------
function RotorBar:setColor(color)
    local r, g, b, a = unpack(color)
    local int_color = make_int_color(r, g, b, a)

    if self.last_int_barcolor ~= int_color then
        self.bar:SetStatusBarColor(r, g, b, a)
        self.last_int_barcolor = int_color
    end
end


----------------------------------------
-- Utilites
----------------------------------------
function Rotor:createBarForBuff(buff)
    local bar = self:createBar('TOP', true, true, false, true, false)
    bar.buff = buff
    bar.tslu = 0
    function bar:update(elapsed, force)
        self.tslu = self.tslu + elapsed
        if force or (self.tslu > 0.1) then
            self.tslu = 0
            if not self.buff.active or not Rotor.visible then
                self:hide()
            else
                self:setValues(0, self.buff.duration, self.buff.time_left, buff.name, nil, format('%.1f', self.buff.time_left))
                self:show()
            end
        end
    end
    return bar
end
